﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using MugenInjection.Delegates;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;


//// ReSharper disable CheckNamespace

namespace MugenInjection.Core.Components
//// ReSharper restore CheckNamespace
{
    /// <summary>
    ///     Represents the class that can convert collection types.
    /// </summary>
    public class CollectionConverter : ConverterBase
    {
        #region Fields

        private static readonly Type[] CollectionTypes = new[]
                                                             {
                                                                 typeof (Array),
                                                                 typeof (IEnumerable<>),
                                                                 typeof (IList<>),
                                                                 typeof (ICollection<>)
                                                             };

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="CollectionConverter" /> class.
        /// </summary>
        public CollectionConverter()
            : base(DefaultPriority, ServiceType.Simple | ServiceType.Activatable | ServiceType.Resolvable)
        {
            UseParentToResolveCollection = true;
        }

        #endregion

        #region Properties

        /// <summary>
        ///     Gets or sets value, if <c>true</c> use parent to resolve collection type values; otherwise <c>false</c>.
        /// </summary>
        public bool UseParentToResolveCollection { get; set; }

        #endregion

        #region Methods

        /// <summary>
        ///     Converts an object(s) to a array.
        /// </summary>
        /// <param name="arrayType">The specified type of array.</param>
        /// <param name="elementType">The specified type of element.</param>
        /// <param name="value">The specified object to convert.</param>
        /// <returns>An instance of collection.</returns>
        protected virtual object ConvertToArray(Type arrayType, Type elementType, object value)
        {
            ConverterContext context = InjectorUtilsInternal.GetConverterContext(value);
            IBindingContext bindingContext = context.BindingContext;
            bindingContext.Service = elementType;
            IList<object> objects = bindingContext.CallInjector.ResolveAll(bindingContext, UseParentToResolveCollection);
            if (objects.Count == 1 && arrayType.IsInstanceOfType(objects[0]))
                return Array.CreateInstance(elementType, 0);

            Array array = Array.CreateInstance(elementType, objects.Count);
            for (int i = 0; i < objects.Count; i++)
            {
                array.SetValue(objects[i], i);
            }
            return array;
        }

        /// <summary>
        ///     Converts an object(s) to a collection
        /// </summary>
        /// <param name="collectionType">The specified type of collection.</param>
        /// <param name="elementType">The specified type of element.</param>
        /// <param name="createCollection">The specified delegate to create collection.</param>
        /// <param name="addMethod">The specified delegate to add element to collection.</param>
        /// <param name="value">The specified object to convert.</param>
        /// <returns>An instance of collection.</returns>
        protected virtual object ConvertToCollection(Type collectionType, Type elementType,
                                                     ActivatorDelegate createCollection,
                                                     InvokeMethodDelegate<object> addMethod, object value)
        {
            ConverterContext context = InjectorUtilsInternal.GetConverterContext(value);
            IBindingContext bindingContext = context.BindingContext;
            bindingContext.Service = elementType;
            IList<object> objects = bindingContext.CallInjector.ResolveAll(bindingContext, UseParentToResolveCollection);
            
            object collection = createCollection();
            foreach (object o in objects)
                addMethod(collection, o);
            return collection;
        }

        #endregion

        #region Overrides of ConverterBase

        /// <summary>
        ///     Gets the supported types.
        /// </summary>
        public override IEnumerable<Type> SupportedTypes
        {
            get { return CollectionTypes; }
        }

        /// <summary>
        ///     Tries to convert instance.
        /// </summary>
        /// <param name="bindingContext">The specified type to convert from.</param>
        /// <param name="originalType">The type to converted to.</param>
        /// <param name="convertAction">An delegate to convert instance.</param>
        /// <returns>
        ///     If <c>true</c> can be converted; otherwise <c>false</c>.
        /// </returns>
        protected override ConverterResult TryConvertInternal(IBindingContext bindingContext, out Type originalType,
                                                              out Func<object, object> convertAction)
        {
            Type service = bindingContext.Service;
            if (service.IsArray)
            {
                Type elementType = service.GetElementType();
                originalType = elementType;
                convertAction = o => ConvertToArray(service, elementType, o);
                return ConverterResult.SupportedWithCache;
            }
            originalType = null;
            convertAction = null;

#if NETFX_CORE
            if (!service.GetTypeInfo().IsGenericType)
#else
            if (!service.IsGenericType)
#endif
                return ConverterResult.NotSupported;
            Type definition = service.GetGenericTypeDefinition();
            ConstructorInfo constructor = null;
            originalType = service.GetGenericArguments()[0];
#if NETFX_CORE
            if (definition.GetTypeInfo().IsInterface)
#else
            if (definition.IsInterface)
#endif
            {
                if (definition == typeof(ICollection<>) || definition == typeof(IEnumerable<>) ||
                    definition == typeof(IList<>))
                    constructor = typeof(List<>).MakeGenericType(originalType).GetConstructor(InjectorUtils.EmptyTypes);
            }
            else
            {
                if (typeof(ICollection<>).MakeGenericType(originalType).IsAssignableFrom(service))
                    constructor = service.GetConstructor(InjectorUtils.EmptyTypes);
            }

            if (constructor == null)
                return ConverterResult.NotSupported;
            MethodInfo methodInfo = constructor.DeclaringType.GetMethod("Add", new[] { originalType });
            ActivatorDelegate activatorDelegate =
                InjectorUtils.ReflectionAccessProvider.GetActivatorDelegate(constructor);
            InvokeMethodDelegate<object> addMethod =
                InjectorUtils.ReflectionAccessProvider.GetInvokeMethodDelegate<object>(methodInfo);
            Type type = originalType;
            convertAction = o => ConvertToCollection(service, type, activatorDelegate, addMethod, o);
            return ConverterResult.SupportedWithCache;
        }

        #endregion
    }
}